<?php
/**
 * Глобальная модель приложения
 * 
 * @category Phorm
 * @package Model
 * @name /Phorm/Model.php
 * @copyright phorm.ru
 * @since 22.11.2010
 * @author vladimir@kachalov.net
 */

class Phorm_Model {
	
	/**
	 * Объект загрузки приложения
	 * @var Phorm_Bootstrap
	 */
	protected $_boot;
	
	/**
	 * Объект запроса
	 * @var Zend_Controller_Request_Http
	 */
	protected $_request;
	
	/**
	 * Параметры запроса
	 * @var array
	 */
	protected $_params;
	
	/**
	 * Массив конфигурации приложения
	 * @var array
	 */
	protected $_cnf;
	
	/**
	 * Объект пользовательской сессии
	 * @var Zend_Session_Abstract
	 */
	public $_usersession;
	
	/**
	 * Объект соединения с базой данных
	 * @var Zend_Db_Adapter_Abstract
	 */
	public $_db;
	
	/**
	 * Имя текущего модуля (админка или сайт)
	 * @var string
	 */
	protected $_module = "default";
	
	/**
	 * Объект переводчика
	 * @var Zend_Translate
	 */
	protected $_translate;
	
	/**
	 * Массив ошибок, возникающих в процессе работы моделей
	 * @var array
	 */
	public $_errors = array();


    /**
     * Конструктор модели
     * @param null|\Zend_Application_Bootstrap_Bootstrap $bootstrap
     */
    
    public function __construct(Zend_Application_Bootstrap_Bootstrap $bootstrap = null) {
    	
    	if($bootstrap instanceof Zend_Application_Bootstrap_Bootstrap) {
			$this->_boot = $bootstrap;
		} else {
			$this->_boot = Zend_Controller_Front::getInstance()->getParam('bootstrap');
		}
    	
		$this->_cnf = $this->_boot->getOptions();
		$this->_request = $this->_boot->getResource('Request');
		$this->_params = $this->_request->getParams();
		$this->_usersession = $this->_boot->getResource('UserSession');
		$this->_db = $this->_boot->getResource('Db');
		$this->_translate = $this->_boot->getResource('Translate');
		
		if(isset($this->_params['module'])) $this->setModule($this->_params['module']);
		
		if(method_exists($this,'init')) $this->init();

    }
    
    
    /**
     * Возвращает объект соединения с базой данных
     *
     * @return Zend_Db
     */
    
    public function getDb() {
    	
    	return $this->_db;
    	
    }
    
    
	/**
	 * Устанавливает текущий модуль MVC (админка или сайт)
	 *
	 * @param string $module default или control
	 * 
	 * @return void
	 */
	
	public function setModule($module) {
		
		$this->_module = $module=='control' ? 'control' : $module;
		
	}
	
	
	/**
	 * Возвращает имя текущего модуля MVC
	 *
	 * @return string
	 */
	
	public function getModule() {
		
		return $this->_module;
		
	}
	
	
    /**
	 * Возвращает массив опций для построения постраничной навигации
	 * Необходимо дергать сразу после выполнения SQL-запроса с SQL_CAL_FOUND_ROWS
	 * 
	 * @param array $options - Массив опций
	 * 
	 * @return object
	 */
    
	function getPaginator($options = array()) {
			
		$Paginator = new Zend_Paginator(new Zend_Paginator_Adapter_Null($this->_db->fetchOne('SELECT FOUND_ROWS()')));
			
		$Paginator -> setItemCountPerPage(isset($options['perpage']) ? $options['perpage'] : $this->getPerPage());
				   
		if(isset($options['widgetid'])) {
			$Paginator -> setCurrentPageNumber($this->getCurrentPage($options['widgetid']));
		} else {
			$Paginator -> setCurrentPageNumber($this->getCurrentPage());
		}
						   
		$out = $Paginator->getPages();
		
		$pageidentity = $this->getPageIdentity();
			
		// Кроме стандартных параметров Zend_Paginator::getPages() возвращаем доп. параметры

		// Полный путь без GET-запроса
		$UrlInfo = parse_url($this->_request->getRequestUri());
		$out->ClearUrl = $UrlInfo['path'];
			
		// Обрабатываем GET-запрос
		$query = $this->_request->getQuery();
		if(isset($query[$pageidentity])) unset($query[$pageidentity]);
		if(isset($query['widget'])) unset($query['widget']);
		
		// Строим строку выражения
		if(!empty($query)) $out->Query = http_build_query($query);
			
		// Фактически, ссылку на первую страницу (без page и widget)
		$out->FullUrl = isset($out->Query) ? $out->ClearUrl.'?'.$out->Query : $out->ClearUrl;
		
		// Добавляем к выражению Id виджета
		$widget = isset($options['widgetid']) ? 'widget='.(int)$options['widgetid'].'&' : '';
 
		// Полную ссылку к которой в конце надо только добавить номер страницы
		$out->PageUrl = isset($out->Query) ? $out->FullUrl.'&'.$widget.$pageidentity.'=' : $out->FullUrl.'?'.$widget.$pageidentity.'=';
			
		return $out;
			
	}

	
	/**
	 * Возвращает текущий номер страницы
	 * 
	 * @param integer $widgetid Id виджета (опционально, вернет номер страницы для заданного виджета)
	 * 
	 * @return int
	 * @todo Убрать привязку к виджету (на странице больше одной постраничной навигации быть не должно)
	 * @todo Была проблема со страницами для других виджетов на странице (надо решить по другому)
	 */
	
	function getCurrentPage($widgetid=0) {
		
		if($widgetid>0 && isset($this->_params['widget']) && $widgetid!=$this->_params['widget']) {
			return 1;
		}
		
		$pageidentity = $this->getPageIdentity();
		
		if(isset($this->_params[$pageidentity]) && (int)$this->_params[$pageidentity]>0) {
			
			return (int)$this->_params[$pageidentity];
			
		} elseif (isset($this->_params[$pageidentity]) && (int)$this->_params[$pageidentity]>0) {
			
			return (int)$this->_params[$pageidentity];
			
		} else {
			
			return 1;
			
		}
			
	}
		
		
	/**
	 * Возвращает кол-во записей на страницу для текущего модуля/контроллера/действия 
	 *
	 * @return int
	 */
	
	function getPerPage() {
		
		if(isset($this->_params['perPage'])) return (int)$this->_params['perPage'];
			
		$module = $this->_params["module"];
		$controller = $this->_params["controller"];
		$action = $this->_params["action"];
			
		if(isset($this->_cnf["modules"][$module]["controllers"][$controller]["actions"][$action]["perpage"])) {
				
			return $this->_cnf["modules"][$module]["controllers"][$controller]["actions"][$action]["perpage"];
				
		} elseif (isset($this->_cnf["modules"][$module]["controllers"][$controller]["perpage"])) {
				
			return $this->_cnf["modules"][$module]["controllers"][$controller]["perpage"];
				
		} elseif (isset($this->_cnf["modules"][$module]["perpage"])) {
				
			return $this->_cnf["modules"][$module]["perpage"];
				
		} elseif (isset($this->_cnf["perpage"])) {
				
			return $this->_cnf["perpage"];
				
		} else {
				
			return 30;
				
		}
			
	}
	
	/**
	 * Возвращает идентификатор переменной, определяющей страницу
	 *
	 * @return string
	 */
	
	public function getPageIdentity() {
		
		$module = $this->_params['module'];
		$controller = $this->_params['controller'];
		$action = $this->_params['action'];
			
		if(isset($this->_cnf['modules'][$module]['controllers'][$controller]['actions'][$action]['pageidentity'])) {
				
			return $this->_cnf["modules"][$module]["controllers"][$controller]["actions"][$action]["pageidentity"];
				
		} elseif (isset($this->_cnf["modules"][$module]["controllers"][$controller]["pageidentity"])) {
				
			return $this->_cnf["modules"][$module]["controllers"][$controller]["pageidentity"];
				
		} elseif (isset($this->_cnf["modules"][$module]["pageidentity"])) {
				
			return $this->_cnf["modules"][$module]["pageidentity"];
				
		} elseif (isset($this->_cnf["pageidentity"])) {
				
			return $this->_cnf["pageidentity"];
			
		} elseif (isset($this->_cnf['site']['navigation']) && $this->_cnf['site']['navigation']=='ru') {
			
			return 'страница';
				
		} else {
				
			return 'page';
				
		}
		
	}
	
	
	
	
	
	// НАДО РЕШИТЬ КУДА СУНУТЬ ДВА СЛЕДУЮЩИХ МЕТОДА
	// ПО ИДЕЕ ОНИ НУЖНЫ ТОЛЬКО В МАТЕРИАЛАХ, ПОКА НУЖНЫ...
	
	/**
	 * Возвращает список разделов (полных цепочек), привязанных к определенному модулю
	 *
	 * @param string $moduleid Id модуля (если модуль не задан, будут выбраны все разделы)
	 * @return array
	 */
	
	public function getCategoriesPathsByModule($moduleid = null) {
		
		$select = $this->_db->select()
				-> distinct()
				-> from(array('AA'=>'pw_categories'))
				-> from(array('BB'=>'pw_categories'),array())
				-> where('BB.cleft BETWEEN AA.cleft AND AA.cright')
				-> order('AA.cleft');
				
		if(!is_null($moduleid)) $select -> where('BB.moduleid=?',$moduleid);
				
		return $this->_db->fetchAll($select);
		
	}
	
	
	/**
	 * Возвращает плоский список разделов, привязанных к определенному модулю
	 *  
	 * @param int $moduleid Id модуля
	 * @return array
	 */
		
	public function getCategoriesListByModule($moduleid = null) {
		
		$select = $this->_db->select()
				-> from('pw_categories',array('categoryid','categoryname'))
				-> order('cleft');
				
		if(!is_null($moduleid)) $select -> where('moduleid=?',$moduleid);
				
		return $this->_db->fetchPairs($select);
			
	}
	
	
	/**
	 * Строит поисковый индекс
	 * 
	 * @param array $options Массив данных для индексирования
	 *  
	 * @return boolean
	 */
		
	public function buildSearchIndex($options=array()) {
			
		$options['indextitle'] = strip_tags($options['indextitle']);
		$options['indextxt'] = strip_tags($options['indextxt']);
			
		if(preg_match_all('#\w#u',$options['indextitle'],$title)) $options['indextitle'] = strtoupper(implode(' ',$title[0]));
		if(preg_match_all('#\w#u',$options['indextxt'],$content)) $options['indextxt'] = strtoupper(implode(' ',$content[0]));
			
		if(isset($options['resourceid'])) {
				
			$where = 'resourceid='.(int)$options['resourceid'];
				
		} elseif (isset($options['categoryid'])) {
				
			$where = 'categoryid='.(int)$options['categoryid'];
					
		} elseif (isset($options['mvcid'])) {
				
			$where = 'mvcid='.(int)$options['mvcid'];
					
		} else {
				
			return false;
					
		}
			
		$this->_db->delete('pw_search_index',$where);
		$this->_db->insert('pw_search_index',$options);
				
		return true;
			
	}
	
	
	/**
	 * Фильтрует массив значений по ключам, являющимися названиями столбцов в указанной таблице
	 *
	 * @param string $tablename Имя таблицы
	 * @param array $values Массив значений (ассоциативный либо числовой двумерный с ассоциативными массивами)
	 * @return array
	 */
	
	protected function getFilteredValuesByTableCols($tablename, array $values = array()) {
		
		$fields = $this->getTableCols($tablename);
		
		$data = array();
		
		foreach ($values as $field=>$value) {
			if(is_array($value)) {
				foreach ($value as $arrfield=>$arrvalue) {
					if(is_int($field) && in_array($arrfield,$fields)) {
						$key = array_search($arrfield,$fields);
						$data[$field][$fields[$key]] = $arrvalue;
					} elseif (is_int($arrfield) && in_array($field,$fields)) {
						$key = array_search($field,$fields);
						$data[$fields[$key]] = $value;
					}
				}
			} else {
				if(in_array($field,$fields)) {
					$key = array_search($field,$fields);
					$data[$fields[$key]] = $value;
				}
			}
		}
		
		return $data;
		
	}
	
	
	/**
	 * Возвращает массив названий столбцов таблицы
	 *
	 * @param string $tablename Имя таблицы
	 * 
	 * @return array
	 */
	
	public function getTableCols($tablename) {
		
		$table = new Zend_Db_Table(array('name'=>$tablename, 'db'=>$this->_db));
		return $table->info(Zend_Db_Table::COLS);
		
	}
	
	
	/**
	 * Возвращает объект кеша определенного ресурса
	 *
	 * @param string $identity Идентификатор ресурса кеша
	 * @return Zend_Cache_Core
	 */
	
	public function getCache($identity) {
		
		return $this->_boot->getPluginResource('Cachemanager')->getCacheManager()->getCache($identity);
		
	}
	
	
	/**
	 * Логирование действий над ресурсами сайта
	 * 
	 * Не забываем выполнять его в рамках трансакции 
	 * В текущем методе манипуляции с базой происходят без трансакций
	 *
	 * @param string $actiontype insert | update | delete | view | null
	 * @param array $data Массив данных для логирования @see таблицу pw_log
	 * @return int | false
	 * @todo Сделать Exception при недостатке полей в массиве данных и отсутствии в таблице полей logid и creatorid
	 * @todo Подумать о целесообразности проверки наличия полей logid и creatorid 
	 */
	
	public function setLog($actiontype,$data) {
		
		if(!isset($this->_usersession->UserData)) return false;
		
		$data += array(
				'actiondate' => time(),
				'userid' => $this->_usersession->UserData['userid'],
				'actiontype' => $actiontype,
				'mvcid' => isset($this->_params['mvcinfo']) ? (int)$this->_params['mvcinfo']['resourceid'] : 0);
		
		if($this->_db->insert('pw_log',$data)) {
			
			$logdata = array(
				'logid' => $logid = $this->_db->lastInsertId()
			);
			if($actiontype == 'insert') $logdata['creatorid'] = $data['userid'];
			
			$logdata = $this->getFilteredValuesByTableCols($data['tablename'],$logdata);
			
			if(!empty($logdata)) {
				$this->_db->update($data['tablename'], $logdata, $data['fieldname'] . '=' . (int)$data['identity']);
			}
			
			return $logid;
			
		}
			
		return false;
		
	}
	
	
	/**
	 * Возвращает информацию о роли
	 *
	 * @param int $roleid
	 * @return array
	 */
	
	public function getRoleInfo($roleid) {
		
		$select = $this->_db->select()
				-> from('pw_roles')
				-> where('isexist=?','yes')
				-> where('roleid=?',$roleid);
				
		return $this->_db->fetchRow($select);
		
	}
		
	

	
	

	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
		

		
		
	/**
	* Возвращает список существующих в системе ролей
	* 
	* @return array
	*/
		
	/*function getRolesList() {
			
		$sql = "SELECT AA.*,(SELECT COUNT(*) FROM pw_users2roles WHERE roleid=AA.roleid) users 
				FROM pw_roles AA 
				ORDER BY AA.roleid";
			
		return $this->db->fetchAll($sql);
			
	}*/
		
	
		
		
	/**
	* Строит MVC-менюшки
	* 
	* @param object $navigation - Zend_View_Helper_Navigation
	* @param string $module - Название модуля
	* @param string $filename - Имя xml-файла с деревом разделов
	*  
	* @return array
	*/
		
	function getMvcMenus($navigation,$module="default",$filename="menu") {
			
		if(!file_exists($this->paths["applct"]."/modules/".$module."/".$filename.".xml")) return false;
				
		$roles = array_values($this->UserData["roles"]);
		$role = $roles[0];
				
		$acl = new Zend_Acl();
				
		$acl->addRole(new Zend_Acl_Role($role));
				
		$sql = "SELECT DISTINCT module,controller FROM pw_mvc_resources";
				
		foreach ($this->db->fetchAll($sql) as $row) {
			$acl -> add(new Zend_Acl_Resource($row["module"].":".$row["controller"]));
		}
			
		foreach ($this->UserData["acl"]["mvc"] as $modulename=>$moduledata) {
			foreach ($moduledata as $controllername=>$controllerdata) {
				$acl -> allow($role,$modulename.":".$controllername,array_keys($controllerdata));
			}
		}
				
		Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($acl);
	    Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole($role);
	    
	    $file = $this->paths["applct"]."modules/".$module."/".$filename.".xml";
	        	
	    $this->UserSession->mvcMenuContainers[$module][$filename] = 
	    $container = new Zend_Navigation(new Zend_Config_Xml($file));
			
	    // Верхнее навигационное меню
			
		$menu = $navigation->menu($container);
		$menu = $menu->setMinDepth(null)->setMaxDepth(null);
		$topmenu = Phorm_Filter_Utf2Win::filter($menu->render());
			
		// Пользовательское меню
			
		$current = $container->findBy("action","users");
		$menu = $navigation->menu($current);
		$menu = $menu->setMinDepth(null)->setMaxDepth(null);
		$usermenu = Phorm_Filter_Utf2Win::filter($menu->render());
				
		// Подразделы (сестры или детки)
					
		$current = $container->findBy("active",true);
		$menu = $navigation->menu($current);
		$menu = $menu->setMinDepth(0)->setMaxDepth(0);
		$branchmenu = Phorm_Filter_Utf2Win::filter($menu->render());
			
		if($branchmenu=="" && method_exists($current,"getParent")) {
			$menu = $navigation->menu($current->getParent());
			$menu = $menu->setMinDepth(0)->setMaxDepth(0);
			$branchmenu = Phorm_Filter_Utf2Win::filter($menu->render());
		}
				
		// Хлебные крошки
			
		$menu = $navigation->breadcrumbs($container)->setSeparator(" / ");
		$breadcrumbs = Phorm_Filter_Utf2Win::filter($menu->render());
			
		        
		return array("topmenu"     => $topmenu,
					 "branchmenu"  => $branchmenu,
					 "usermenu"	   => $usermenu,
					 "breadcrumbs" => $breadcrumbs);
	}
		
		
	/**
	* Возвращает объект кеша массивов
	* 
	* @param int $lifetime Время жизни кэша в секундах
	* 
	* @return Zend_Cache_Core
	*/
		
	function getCacheArrays($lifetime=7200) {
			
		$frontendOptions = array(
		   'lifetime' => $lifetime,
		   'automatic_serialization' => true
		);
			 
		$backendOptions = array(
		    'cache_dir' => $this->paths["applct"]."cache/Arrays/" // директория, в которой размещаются файлы кэша
		);
			 
		// получение объекта Zend_Cache_Core
		$cache = Zend_Cache::factory('Core',
	                           		 'File',
	                           		 $frontendOptions,
	                           		 $backendOptions);
			                             
		return $cache;
			
	}
	
		
	/**
	* Возвращает информацию о текущем MVC-ресурсе
	*  
	* @return array
	*/
		
	function getCurrentMvcResourceInfo() {
			
		$sql = "SELECT * FROM pw_mvc_resources 
				WHERE module='".$this->_params["module"]."' 
				  AND controller='".$this->_params["controller"]."' 
				  AND action='".$this->_params["action"]."'";
			
		return $this->_db->fetchRow($sql);
			
	}
		
		
	/**
	* Возвращает список слоев для отображения в дизайне
	*  
	* @return array
	*/
	
	function getLayoutsList() {
			
		$sql = "SELECT * FROM pw_layouts WHERE visible='yes' ORDER BY position";
			
		return $this->db->fetchAll($sql);
			
	}
		
		
	/**
	* Возвращает SQL-запрос для выборки всех разделов с полными путями
	*  
	* @return string
	*/
	
	function getCategoriesCondition() {
			
		return "SELECT BBB.*, SUBSTR(CONCAT(GROUP_CONCAT(CASE WHEN AAA.visible='yes' THEN AAA.categorypath ELSE '-' END ORDER BY AAA.cleft SEPARATOR '/'),'/'),2) fullpath 
				FROM pw_categories AAA 
				JOIN pw_categories BBB ON AAA.cleft<=BBB.cleft AND AAA.cright>=BBB.cright
				GROUP BY BBB.categoryid";

	}

	
	/**
	 * Возвращает список значений прав доступа к разделам и материалам в них
	 * 
	 * @return array
	 */
	
	function getCategoriesAclValues() {
			
		return array("deny" => $this->_translate->_("Доступ запрещен и не отображать"),
					 "listmenu" => $this->_translate->_("Доступ запрещен и отображать"),
					 "list" => $this->_translate->_("Просмотр раздела"),
					 "view" => $this->_translate->_("Просмотр материалов"),
					 "add" => $this->_translate->_("Размещение материалов в разделе"),
					 "editself" => $this->_translate->_("Редактирование своих материалов в разделе"),
					 "deleteself" => $this->_translate->_("Удаление своих материалов в разделе"),
					 "edit" => $this->_translate->_("Редактирование любых материалов в разделе"),
					 "delete" => $this->_translate->_("Удаление любых материалов в разделе"),
					 "full" => $this->_translate->_("Полный доступ"));
	}
	
}